﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Agr.CQRS.Infrastructure.Orm;
using Agr.CQRS.Infrastructure.ServiceBus;
using Agr.CQRS.Infrastructure.Events;
using Agr.CQRS.Util;
using Agr.CQRS.Domain.Events;
using Ninject;
using log4net;

namespace Agr.CQRS.ViewModel
{
    public abstract class ViewModelUpdater : IDisposable
    {
        private static ILog Logger = LogManager.GetLogger(typeof(ViewModelUpdater));

        private OrmContext ormContext;
        protected SessionInTransaction tx;
        private Object lockObject;
        DomainEventWatcher watcher;

        public ViewModelUpdater(OrmContext ctx)
        {
            lockObject = new Object();

            ormContext = ctx;
            watcher = new DomainEventWatcher(ctx);
            watcher.NewDomainEventsReceived += handle;
            watcher.Start(this.GetType().Name);
        }

        private void handle(object sender, NewDomainEventsReceivedEventArgs ev)
        {
            lock (lockObject)
                using (tx = ormContext.OpenSession())
                {
                    foreach (var domainEvent in ev.Events)
                    {
                        OnHandling(domainEvent);
                        //find methods that handles this event
                        var method = Reflection.FindMethodWithOneParameterOfType(this.GetType(), domainEvent.GetType());
                        if (method != null)
                        {
                            Logger.InfoFormat("Event {0} is handled", domainEvent.ToString());
                            method.Invoke(this, new object[] { domainEvent });
                        }
                    }
                    tx.Commit();
                }
        }

        protected virtual void OnHandling(DomainEvent ev) { }

        internal void Stop()
        {
            Dispose();
        }

        public void Dispose()
        {
            if (watcher != null)
            {
                watcher.NewDomainEventsReceived -= handle;
                watcher = null;
                watcher.Stop();
            }
        }
    }

    public abstract class ViewModelUpdater<T> : ViewModelUpdater where T : AbstractViewModel, new()
    {
        public ViewModelUpdater(OrmContext ctx)
            : base(ctx)
        {
        }

        protected T CurrentRecord;

        protected override void OnHandling(DomainEvent ev)
        {
            base.OnHandling(ev);
            if (CurrentRecord == null || CurrentRecord.Version != ev.Version || CurrentRecord.AggregateRootId != ev.AggregateRootId)
            {
                CurrentRecord =
                    tx.UseIn<QueryViewModel<T>>()
                        .WhereAggregateRootIs(ev.AggregateRootId)
                        .Result();

                if (CurrentRecord == null)
                {
                    CurrentRecord = new T { AggregateRootId = ev.AggregateRootId };
                    tx.Insert(CurrentRecord);
                }
                CurrentRecord.Version = ev.Version;
            }
        }
    }
}
